﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DirectX.Capture;
using System.Diagnostics;
using System.Threading;
using System.IO;
using Microsoft.DirectX.AudioVideoPlayback;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using DDAUtils.Collections;
using System.Windows.Media.Imaging;

namespace CurlCamWindow
{
    public enum Argument { Null, Delay, VideoDevice, VideoCompressor, Output }

    public partial class CurlCamWindow : Form
    {
        Capture capture = null;
        Filter videoDevice, videoCompressor;
        string output = null;
        MemoryStream[] buffer;
        CircularBuffer<DateTime> fpsBuffer = new CircularBuffer<DateTime>(100, true);
        int /*bufStart, */bufEnd, frameDiff = 0, delay = 10, lines = 5, hackX = 50, hackY = 10, lineSpread = 15;
        bool playDelay = false;
        Capture.HeFrame frameHandler;

        #region Properties
        public long MS
        {
            get
            {
                DateTime dt = DateTime.Now;
                long result = dt.Millisecond, mod = 1000;
                result += dt.Second * mod;
                mod *= 60;
                result += ((long)dt.Minute) * mod;
                mod *= 60;
                result += ((long)dt.Hour) * mod;
                mod *= 24;
                result += ((long)dt.DayOfYear) * mod;
                return result;
            }
        }

        public decimal FPS
        {
            get
            {
                DateTime first = fpsBuffer.Peek();
                DateTime last = fpsBuffer.PeekEnd();
                TimeSpan diff = last - first;
                var msPerFrame = ((decimal)diff.TotalMilliseconds) / (decimal)fpsBuffer.Size;
                return 1 / (msPerFrame / 1000);
            }
        }
        #endregion

        #region Init form
        public CurlCamWindow()
        {
            InitializeComponent();
            VideoDelayTrackBar.Maximum = Const.MAX_DELAY;
            Filters filters = new Filters();
            Argument argKey = Argument.Null;
            foreach (string arg in Environment.GetCommandLineArgs().Skip(1))
            {
                if (arg.IndexOf("--") == 0) //key
                {
                    if (argKey != Argument.Null)
                        throw new ArgumentException("An argument is missing its value");
                    switch (arg)
                    {
                        case "--delay": argKey = Argument.Delay; break;
                        case "--video-device": argKey = Argument.VideoDevice; break;
                        case "--video-compressor": argKey = Argument.VideoCompressor; break;
                        case "--output": argKey = Argument.Output; break;
                        default: throw new ArgumentException("Invalid argument key: " + arg);
                    }
                }
                else //value
                {
                    switch (argKey)
                    {
                        case Argument.Null:
                            throw new ArgumentException("Invalid argument syntax");
                        case Argument.Delay:
                            delay = int.Parse(arg.Replace("delay:", ""));
                            VideoDelayTrackBar.Value = delay;
                            break;
                        case Argument.VideoDevice:
                            videoDevice = filters.VideoInputDevices[arg.Replace("video-device:", "")];
                            break;
                        case Argument.VideoCompressor:
                            videoCompressor = filters.VideoCompressors[arg.Replace("video-compressor:", "")];
                            break;
                        case Argument.Output:
                            output = Path.Combine(arg, "capture-" + DateTime.Now.ToString("yyyy-MM-dd HHmmss") + ".avi");
                            break;
                    }
                    argKey = Argument.Null;
                }
            }

            InitCapture();
            this.FormClosing += new FormClosingEventHandler(CurlCamWindow_FormClosing);
        }

        public void ReInit()
        {
            VideoDelayPictureBox.Paint -= new PaintEventHandler(VideoDelayPictureBox_Paint);
            VideoDelayPictureBox.Image = null;
            capture.Stop();
            capture.Dispose();
            playDelay = false;
            //bufStart = 0;
            bufEnd = 0;

            InitCapture();
        }

        public void InitCapture()
        {
            capture = new Capture(videoDevice, null);
            capture.VideoCompressor = videoCompressor;

            capture.PreviewWindow = VideoLivePanel;

            capture.Filename = output;

            buffer = new MemoryStream[Const.MAX_DELAY * Const.MAX_FPS];

            System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
            t.Interval = delay * 1000;
            t.Tick += new EventHandler((s, e) =>
            {
                playDelay = true;
                t.Stop();
            });
            t.Start();

            VideoDelayPictureBox.Paint += new PaintEventHandler(VideoDelayPictureBox_Paint);

            frameHandler = new Capture.HeFrame(Capture_FrameEvent);
            capture.FrameEvent2 += frameHandler;
            capture.GrapImg();
            capture.Start();
        }
        #endregion

        #region capture events
        void VideoDelayPictureBox_Paint(object sender, PaintEventArgs e)
        {
            UI(() =>
            {
                if (VideoDelayPictureBox.Image != null)
                {
                    Graphics g = e.Graphics;
                    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

                    int realHackX = ((int)g.ClipBounds.Width / 100) * hackX;
                    int realHackY = ((int)g.ClipBounds.Height / 100) * hackY;
                    int realLineSpread = ((int)g.ClipBounds.Width / 100) * lineSpread;
                    int lineOffset = ((int)g.ClipBounds.Width - realLineSpread) / 2;
                    int lineSpacing = realLineSpread / lines;
                    for (int i = 0; i < lines; i++)
                        g.DrawLine(Pens.Black, realHackX, realHackY, lineOffset + (lineSpacing * i), g.ClipBounds.Height);
                }
            });
        }

        public void Capture_FrameEvent(Bitmap b)
        {
            UI(() =>
            {
                fpsBuffer.Put(DateTime.Now);
                MemoryStream ms = new MemoryStream();
                b.Save(ms, ImageFormat.Png);
                /*if (buffer[bufEnd] != null)
                    buffer[bufEnd].Dispose();*/
                buffer[bufEnd] = ms;
                b.Dispose();
                GC.Collect();
                
                bufEnd++;
                if (playDelay)
                {
                    frameDiff = bufEnd - (int)(FPS * delay);
                    if (frameDiff < 0)
                        frameDiff += buffer.Length;
                    if (buffer[frameDiff] != null)
                        VideoDelayPictureBox.Image = Bitmap.FromStream(buffer[frameDiff]);
                }
                if (bufEnd + 1 == buffer.Length)
                    bufEnd = 0;

                label1.Text = "" + frameDiff;//bufStart;
                label2.Text = "" + bufEnd;
                if (frameDiff > 0)
                    label3.Text = "" + FPS;
            });
        }
        #endregion

        #region UI-related events
        public void CurlCamWindow_FormClosing(object sender, FormClosingEventArgs e)
        {
            capture.Stop();
            capture.Dispose();
        }

        private void VideoDelayTrackBar_ValueChanged(object sender, EventArgs e)
        {
            delay = VideoDelayTrackBar.Value;
            VideoDelayLabel.Text = String.Format("Delay: {0} sec", delay);
        }

        private void UI(Action a)
        {
            if (!this.IsHandleCreated)
            {
                a();
                return;
            }
            this.Invoke((MethodInvoker)delegate 
            { 
                a();
            });
        }
        #endregion
    }

    public static class Const
    {
        public const int MAX_DELAY = 60;
        public const int MAX_FPS = 35;
    }
}